package org.sk.gsync.app.synch;

import java.io.IOException;
import java.util.Collection;
import java.util.Map;

import org.apache.log4j.Logger;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.gdata.client.media.ResumableGDataFileUploader;
import com.google.gdata.client.uploader.FileUploadData;
import com.google.gdata.client.uploader.ProgressListener;
import com.google.gdata.client.uploader.ResumableHttpFileUploader;
import com.google.gdata.data.Link;
import com.google.gdata.data.docs.DocumentListEntry;
import com.google.gdata.util.ServiceException;

/**
 * A {@link ProgressListener} implementation to track upload progress. The
 * listener can track multiple uploads at the same time. Use {@link #isDone} to
 * check if all uploads are completed and use {@link #getUploaded} to access
 * results of successful uploads.
 */
public class FileUploadProgressListener implements ProgressListener {

    private final Collection<ResumableGDataFileUploader> trackedUploaders =
        Lists.newArrayList();

    private int pendingRequests;

    Map<String, DocumentListEntry> uploaded = Maps.newHashMap();

    Map<String, String> failed = Maps.newHashMap();

    private static final Logger LOGGER = Logger
        .getLogger(FileUploadProgressListener.class);

    boolean processed;

    public FileUploadProgressListener() {
        pendingRequests = 0;
    }

    public void listenTo(
            final Collection<ResumableGDataFileUploader> uploaders) {
        trackedUploaders.addAll(uploaders);
        pendingRequests = trackedUploaders.size();
    }

    public synchronized void progressChanged(
            final ResumableHttpFileUploader uploader) {
        String fileId =
            ((FileUploadData) uploader.getData()).getFileName();
        switch (uploader.getUploadState()) {
        case COMPLETE:
        case CLIENT_ERROR:
            pendingRequests -= 1;
            LOGGER.info(fileId + ": Completed");
            break;
        case IN_PROGRESS:
            LOGGER.info(fileId + ":"
                + String.format("%3.0f", uploader.getProgress() * 100)
                + "%");
            break;
        case NOT_STARTED:
            LOGGER.info(fileId + ":" + "Not Started");
            break;
        }
    }

    public synchronized boolean isDone() {
        // not done if there are any pending requests.
        if (pendingRequests > 0) {
            return false;
        }
        // if all responses are processed., nothing to do.
        if (processed) {
            return true;
        }
        // check if all response streams are available.
        for (ResumableGDataFileUploader uploader : trackedUploaders) {
            if (!uploader.isDone()) {
                return false;
            }
        }
        // process all responses
        for (ResumableGDataFileUploader uploader : trackedUploaders) {
            String fileId =
                ((FileUploadData) uploader.getData()).getFileName();
            switch (uploader.getUploadState()) {
            case COMPLETE:
                try {
                    DocumentListEntry entry =
                        uploader.getResponse(DocumentListEntry.class);
                    uploaded.put(fileId, entry);
                } catch (IOException e) {
                    failed.put(fileId,
                        "Upload completed, but unexpected error "
                            + "reading server response");
                } catch (ServiceException e) {
                    failed
                        .put(fileId,
                            "Upload completed, but failed to parse server response");
                }
                break;
            case CLIENT_ERROR:
                failed.put(fileId, "Failed at " + uploader.getProgress());
                break;
            }
        }
        processed = true;
        LOGGER.info("All requests done");
        return true;
    }

    public synchronized Collection<DocumentListEntry> getUploaded() {
        if (!isDone()) {
            return null;
        }
        return uploaded.values();
    }

    public synchronized void printResults() {
        if (!isDone()) {
            return;
        }
        LOGGER.info("Result: " + uploaded.size() + ", " + failed.size());
        if (uploaded.size() > 0) {
            LOGGER.info(" Successfully Uploaded:");
            for (Map.Entry<String, DocumentListEntry> entry : uploaded
                .entrySet()) {
                printDocumentEntry(entry.getValue());
            }
        }
        if (failed.size() > 0) {
            LOGGER.info(" Failed to upload:");
            for (Map.Entry entry : failed.entrySet()) {
                LOGGER
                    .info("  " + entry.getKey() + ":" + entry.getValue());
            }
        }
    }

    /**
     * Prints out the specified document entry.
     * 
     * @param doc
     *            the document entry to print.
     */
    public void printDocumentEntry(final DocumentListEntry doc) {
        StringBuffer buffer = new StringBuffer();

        buffer.append(" -- " + doc.getTitle().getPlainText() + " ");
        if (!doc.getParentLinks().isEmpty()) {
            for (Link link : doc.getParentLinks()) {
                buffer.append("[" + link.getTitle() + "] ");
            }
        }
        buffer.append(doc.getResourceId());

        LOGGER.info(buffer);
    }

}
